// LotroLCD.cpp

#include "stdafx.h"
#include "EZ_LCD.h"
#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#include <sstream>
#include <vector>
#include <numeric>
#include <time.h>
#include <algorithm>
#include <psapi.h>
#include <shlobj.h>
#include <shlobj.h>
#include <map>
#include "LimitSingleInstance.h"
#include "LotroLCD.h"

// Memory Profiling
//#define _CRTDBG_MAP_ALLOC
//#include <stdlib.h>
//#include <crtdbg.h>

// Macros for code readability
#define RECURSIVE true
#define NON_RECURSIVE false

// Declare error handling & other functions
void DisplayError(LPTSTR lpszFunction, CString extraMessage = "");
int ReadNewLines(HANDLE *hFile, CString* logChanges);
int CloseFile(HANDLE *hFile);
int WriteLotroPluginData(HANDLE *hFile, map<CString, CString>* pluginVariables);
int setPluginVariable(map<CString, CString>* pluginVariables, CString key, int value);
int setPluginVariable(map<CString, CString>* pluginVariables, CString key, CString value);
int OpenMostRecentFile(HANDLE *hFile, CString directoryPath, DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCreationDisposition, CString searchPattern ="*",  bool recursive = false );
int PopulateLCD(CEzLcd* lcd, HANDLE* fieldArray, HANDLE* pbarArray, HANDLE* icon, int fields, int vSpacing, int hSpacing, int linesPerRow, HINSTANCE hInstance);
int ParseLines(CString inputString, vector<CString>* messageQueue, bool filterDups);
int IndexLines(vector<CString>* inputLines, vector<vector<int>>* timerIndex);
int parseCombatLine(vector<CString>* knownPetNames, vector<CString>* skirmishPetNames, CString combatLine, CString* enemyName, int* dmg);
CStringW UTF8toUTF16(const CStringA& utf8);
time_t lastLogMessage = time(NULL);

// The one and only LimitSingleInstance object.
LimitSingleInstance g_Instance(_T("{c82caae8-9d52-4aef-96fc-92fd57a610f2}"));

// Entry Point under Standard Windows Libraries
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE /*hPrevInstance*/,
                     LPSTR     /*lpCmdLine*/,
                     int       /*nCmdShow*/)
{
	lotroLCD(&hInstance);
	return 0;
}


UINT lotroLCD(LPVOID Param) //, HINSTANCE hInstance
{
	HINSTANCE hInstance = (HINSTANCE)Param;

	// Check if application is already running, if so warn and exit
	if(g_Instance.IsAnotherInstanceRunning())
    {
		DisplayError(_T("LotroLCD is already running, stop the existing process before starting another!\n\n"));
        return 1;
    }

    // Create instance of EzLcd and initialize with friendly name and size of lcd and update
 	CEzLcd* lcd = new CEzLcd(_T("Lotro LCD"), 160, 43);
	lcd->Update();

	// Define paths and file pattern, get user home directory and documents from windows
	wchar_t documentsPath[MAX_PATH];
	SHGetFolderPathW(NULL, CSIDL_PERSONAL|CSIDL_FLAG_CREATE,NULL, SHGFP_TYPE_CURRENT, documentsPath);
	CString documentsDir = documentsPath;
	CString logFileDir = documentsDir + _T("\\The Lord of the Rings Online");
	CString IMLogPattern = _T("IMs_*.txt");
	CString combatLogPattern = _T("Combat_*.txt");
	CString pluginDataDir = logFileDir + _T("\\PluginData");
	CString pluginDataPattern = _T("LoadData.plugindata");

	// Combat History, array of vectors, stores combat time, dmg in that order) for all combats from start or clear
	vector<int>combatHistory[2];

	// Strings to hold new log entries
	CString combatLogChanges;
	CString IMLogChanges;

	// Handles to log files
	HANDLE hIMLogFile = INVALID_HANDLE_VALUE;
	HANDLE hCombatLogFile = INVALID_HANDLE_VALUE;
	HANDLE hPluginDataFile = INVALID_HANDLE_VALUE;

	// LCD Parameters (to be externally controlled at a later time)
	UINT fields = 6, fieldRatio = fields/2, vSpacing = 15, hSpacing = 80, linesPerRow = 3;

	// Handles for LCD objects
	HANDLE* fieldArray = new HANDLE[fields];
	HANDLE* pbarArray = new HANDLE[fields];
	HANDLE icon = new HANDLE;

	// Define LCD screen layout, get handle arrays to objects on screen
	PopulateLCD(lcd, fieldArray, pbarArray, &icon, fields, vSpacing, hSpacing, linesPerRow, hInstance);

	// Open plugin data file
	OpenMostRecentFile(&hPluginDataFile, pluginDataDir, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ |FILE_SHARE_WRITE, CREATE_ALWAYS, pluginDataPattern, RECURSIVE);

	// Open log files, if combat and chat logs are not found we wait until they are created
	while (OpenMostRecentFile(&hIMLogFile, logFileDir, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING, IMLogPattern, NON_RECURSIVE) || OpenMostRecentFile(&hCombatLogFile, logFileDir, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING, combatLogPattern, NON_RECURSIVE))
	{
		CloseFile(&hIMLogFile);
		CloseFile(&hCombatLogFile);
		Sleep(50);
		if (lcd->IsConnected())
		{
			lcd->SetVisible(fieldArray[0], TRUE);
			lcd->SetVisible(fieldArray[1], TRUE);
			lcd->SetVisible(fieldArray[2], TRUE);
			lcd->SetVisible(fieldArray[3], TRUE);
			lcd->SetVisible(fieldArray[4], TRUE);
			lcd->SetVisible(fieldArray[5], TRUE);
			lcd->SetText(fieldArray[0],_T("   Combat or IM log"));
			lcd->SetText(fieldArray[1],_T("   Start logging both"));
			lcd->SetText(fieldArray[2],_T("    See ReadMe.txt"));
			lcd->SetText(fieldArray[3],_T("missing."));
			lcd->SetText(fieldArray[4],_T("in the Lotro client"));
			lcd->SetText(fieldArray[5],_T("for more information"));			
		} else {
			OpenMostRecentFile(&hIMLogFile, logFileDir, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ |FILE_SHARE_WRITE, CREATE_NEW, _T("IMs_20000101_1.txt"), NON_RECURSIVE);
			OpenMostRecentFile(&hCombatLogFile, logFileDir, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ |FILE_SHARE_WRITE, CREATE_NEW, _T("Combat_20000101_1.txt"), NON_RECURSIVE);
			//DisplayError(_T("Error finding Logitech keyboard and LOTRO log files :"));
			//return 1;
		}
		lcd->Update();
	}
	if (lcd->IsConnected())
	{
		//Cleanup if files were not found and have now been found
		lcd->SetVisible(fieldArray[0], FALSE);
		lcd->SetVisible(fieldArray[1], FALSE);
		lcd->SetVisible(fieldArray[2], FALSE);
		lcd->SetVisible(fieldArray[3], FALSE);
		lcd->SetVisible(fieldArray[4], FALSE);
		lcd->SetVisible(fieldArray[5], FALSE);
		lcd->SetText(fieldArray[0],_T(""));
		lcd->SetText(fieldArray[1],_T(""));
		lcd->SetText(fieldArray[2],_T(""));
		lcd->SetText(fieldArray[3],_T(""));
		lcd->SetText(fieldArray[4],_T(""));
		lcd->SetText(fieldArray[5],_T(""));
	}
	
	// Read initial file contents and do nothing with it
	ReadNewLines(&hIMLogFile, &IMLogChanges);
	ReadNewLines(&hCombatLogFile, &combatLogChanges);

	// Create message queues for new lines in the log files.
	// Create a place to compile lines with decremented timers
	// Create a place to store the position of time and timers
	vector<CString> IMMessageQueue;
	vector<vector<int>> timerIndex; //first int is a timestamp, followed by locations of control chars
	vector<vector<int>> combatTime; //only the timestamp is used we use same struct/func as timerIndex
	vector<int> dmgOutByType(10,0);
	vector<int> dmgInByType(10,0);
	vector<float> timerPercentages; 
	vector<float> dmgTypeOutPercentages(10,0);
	vector<float> dmgTypeInPercentages(10,0);
	vector<CString> compiledMessageQueue;
	vector<CString> combatMessageQueue;
	vector<CString> compiledCombatQueue;
	vector<int> eraseList;
	CString skirmishPetNamesArray[] = {_T("Herbalist"), _T("Sage"), _T("Protector"), _T("Archer"), _T("Warrior"), _T("Bannerguard")};
	CString defaultPetNamesArray[] = {_T("Raven"), _T("Bear"), _T("Lynx"), _T("Lesser Giant Eagle"), _T("Sabretooth"), _T("Bog-lurker")};
	vector<CString> skirmishPetNames(skirmishPetNamesArray,skirmishPetNamesArray+6);
	vector<CString> knownPetNames(defaultPetNamesArray,defaultPetNamesArray+6);

	// Track when fight starts, last damage done when and how much inactivity is allowed before a fight is considered over.
	// Grace period for out of order log messages (death message comes before all dmg messages have been delivered)
	time_t combatStartTime = time(NULL), combatLastDmgTime = 0, combatInactivityTimer = 30, combatEndGracePeriod = 3, totalCombatTime = 1;;

	// Counter for checking if there are new log files and timestamp of the last log message
	time_t lastFileCheck = time(NULL);
	time_t lastPluginFileCheck = time(NULL);

	// Track active enemies
	vector<CString> enemyList;

	// Track flanking 
	vector<CString> flankedEnemy;
	vector<time_t> flankedTimeStamp;
	CString numeratedFlankedEnemy;
	CString numeratedFlankedTime;

	// Track defeat messages which are out of order
	vector<CString> outOfOrderDefeats;

	// Track total dmg dealt and taken for self and pet
	int selfTotalDmgIn = 0, selfTotalDmgOut = 0, petTotalDmgIn = 0, petTotalDmgOut = 0, skirmPetTotalDmgIn = 0, skirmPetTotalDmgOut = 0, selfTotalHealIn = 0, selfTotalHealOut = 0;

	// Keep track of changes so we only update the LCD when needed and show what is wanted
	bool updateLayout = true;
	bool updateQueue = false;
	bool showHealing = false;

	// Keep track of when we succumb to our wounds
	bool succumbDetected = false;

	// Track Variables to output to plugin data file
	map<CString, CString> pluginVariables; // map of plugin variables and their values

    // Main loop
	while(1)
    {
        // Check buttons and take action if needed
		if (lcd->IsConnected() && lcd->ButtonTriggered(LG_BUTTON_1))
        {
            TRACE(_T("Button 1 triggered\n"));
			// Clear all queues/messages
			IMMessageQueue.clear();
			timerIndex.clear();
			combatMessageQueue.clear();
			combatTime.clear();
			timerPercentages.clear();
			compiledCombatQueue.clear();
			// Clear all enemies from enemy list
			enemyList.clear();
			eraseList.clear();
			outOfOrderDefeats.clear();
			// Clear Combat History
			combatHistory[0].clear();
			combatHistory[1].clear();
			// Clear dmg statistics
			dmgOutByType.assign(10,0);
			dmgInByType.assign(10,0);
			dmgTypeOutPercentages.assign(10,0);
			dmgTypeInPercentages.assign(10,0);
			selfTotalDmgOut = 0;
			selfTotalDmgIn = 0;
			petTotalDmgOut = 0;
			petTotalDmgIn = 0;
			skirmPetTotalDmgOut = 0;
			skirmPetTotalDmgIn = 0;
			totalCombatTime = 1;
			// Close open files
			CloseFile(&hIMLogFile);
			CloseFile(&hCombatLogFile);
			// Open most recent files
			OpenMostRecentFile(&hIMLogFile, logFileDir, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING, IMLogPattern, NON_RECURSIVE);
			OpenMostRecentFile(&hCombatLogFile, logFileDir, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING, combatLogPattern, NON_RECURSIVE);
			OpenMostRecentFile(&hPluginDataFile, pluginDataDir, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ |FILE_SHARE_WRITE, CREATE_ALWAYS, pluginDataPattern, RECURSIVE);
			// Read to end of file, ignore the data
			if (ReadNewLines(&hIMLogFile, &IMLogChanges) || ReadNewLines(&hCombatLogFile, &combatLogChanges))
			{
				DisplayError(_T("ReadNewLines"));
				CloseFile(&hIMLogFile);
				CloseFile(&hCombatLogFile);
				return 1;
			}
			updateQueue = true;
			// Dump Memory Leak Information for detecting memory leaks
			//_CrtDumpMemoryLeaks();
        }
		if (lcd->IsConnected() && lcd->ButtonTriggered(LG_BUTTON_2))
        {
            TRACE(_T("Button 2 triggered\n"));
			fieldRatio = fields/2;
			updateLayout = true;
			updateQueue = true;
        }
		if (lcd->IsConnected() && lcd->ButtonTriggered(LG_BUTTON_3))
        {
            TRACE(_T("Button 3 triggered\n"));
			fieldRatio = fields;
			updateLayout = true;
			updateQueue = true;
        }
		if (lcd->IsConnected() && lcd->ButtonTriggered(LG_BUTTON_4))
        {
            TRACE(_T("Button 4 triggered\n"));
			if (fieldRatio == 0 && showHealing == false) // Toggle healing data
			{
				showHealing = true;
			} else if (fieldRatio == 0 && showHealing == true)
			{
				showHealing = false;
			}
			fieldRatio = 0;
			updateLayout = true;
			updateQueue = true;
			
        }

		if (updateLayout)
		// If a button has been pressed we update the visibility of objects(layout) accordingly
		{
			// Set LCD Layout according to request
			for (UINT i = 0; i < fieldRatio; i++)
			{
				if (lcd->IsConnected())
				{
					lcd->SetVisible(pbarArray[i], TRUE);
					lcd->SetVisible(fieldArray[i], TRUE);
				}
			}
			for (UINT i = fieldRatio; i < fields; i++)
			{
				if (lcd->IsConnected())
				{
					lcd->SetVisible(pbarArray[i], TRUE);
					lcd->SetVisible(fieldArray[i], TRUE);
				}
			}
			updateLayout = false;
		}

		// Get updates to log files
		if (ReadNewLines(&hIMLogFile, &IMLogChanges) || ReadNewLines(&hCombatLogFile, &combatLogChanges) )
		{
			DisplayError(_T("ReadNewLines"));
			CloseFile(&hIMLogFile);
			CloseFile(&hCombatLogFile);
			return 1;
		}

		// Parse combat log lines into vector and add timestamp per line
		if (combatLogChanges.GetLength() > 0)
		{
			if (ParseLines(combatLogChanges, &combatMessageQueue, false) || IndexLines(&combatMessageQueue, &combatTime))
			{
				DisplayError(_T("ParseLines"));
				CloseFile(&hIMLogFile);
				CloseFile(&hCombatLogFile);
				return 1;
			}
		}

		// Parse IM Log lines into vector and index for time/timer tags
		if (IMLogChanges.GetLength() > 0)
		{
			if (ParseLines(IMLogChanges, &IMMessageQueue, true) || IndexLines(&IMMessageQueue, &timerIndex))
			{
				DisplayError(_T("ParseLines"));
				CloseFile(&hIMLogFile);
				CloseFile(&hCombatLogFile);
				return 1;
			}
		}

		// Clear vector but if has previously contained data make sure we update LCD
		if (compiledMessageQueue.size() > 0)
		{
			compiledMessageQueue.clear();
			updateQueue = true;
			// Clear write queue for plugin data file
			pluginVariables.clear();
		}
		// Populate a new vector with updated timers updating timers inside brackets,
		// hiding control characters and ignoring timestamp in file
		for(UINT i = 0;i < IMMessageQueue.size(); i++)
		{
			// Check to see that line has at least two brackets and one timestamp, if not it's  considered malformed
			// and dumped to screen without change, this can only happen if the log file is 
			// edited by hand since proper log lines always have at least a timestamp written by lotro
			CString workingLine;
			int startOfRealMSG = IMMessageQueue.at(i).Find(_T(": "))+1; //after 'CharName: '
			time_t lineExpiresAfter = LONG_MIN;
			bool duplicateStaticLine = false;
			timerPercentages.push_back(100); // Default state of progress bars is 100%
			if (timerIndex.at(i).size() > 3)
			{
				time_t timeStamp = timerIndex.at(i).at(0); // Index always starts with the timestamp
				int bracketPos = 0, prevBracketPos = 0;
				for(UINT j = 3;j < timerIndex.at(i).size() ; j++) // Ignore time at start of line
				{
					prevBracketPos = timerIndex.at(i).at(j-1);
					bracketPos = timerIndex.at(i).at(j);
					if (IMMessageQueue.at(i)[bracketPos] == '[') // Segment contains text and ends with opening bracket
					{
						// Ignore 'CharName: ' if it's present in the line
						if (prevBracketPos < startOfRealMSG)
						{
							prevBracketPos = startOfRealMSG;
						}
						workingLine.Append(IMMessageQueue.at(i).Mid(prevBracketPos+1,(bracketPos-prevBracketPos)-1));
					} else if (IMMessageQueue.at(i)[bracketPos] == ']') // Segement contains timer and ends with closing bracket
					{
						// Try getting any numerical values from the string between the [] brackets, if there are any
						// we decrement timer from original using the timestamp when the line was read
						// and add the timer back into the line
						
						unsigned int timerInitial = _tcstol(IMMessageQueue.at(i).Mid(prevBracketPos+1,bracketPos-prevBracketPos),NULL,10);
						if (timerInitial)
						{
							time_t currentTime = time(NULL);
								time_t timerTimeLeft =((timerInitial + timeStamp) - currentTime);
							CString timerCurrentCount;
							timerCurrentCount.Format(L"%i", timerTimeLeft);
							workingLine.Append(timerCurrentCount);
							// Set lines to expire when largest timer expires
							// Set progress bar percentage to largest timer
							if (timerTimeLeft > lineExpiresAfter)
							{
								lineExpiresAfter = timerTimeLeft;
								timerPercentages[i] = (float)(100 * timerTimeLeft) / timerInitial;
							}
						}
					} else {
						// If for some reason bracketPos does not point to a bracket
						DisplayError(_T("BracketPosNotBracket"));
						CloseFile(&hIMLogFile);
						CloseFile(&hCombatLogFile);
						return 1;
					}
				}
				// If the line does not end with bracket we add the trailing text
				int lineLength = IMMessageQueue.at(i).GetLength();
				if (timerIndex.at(i).back() != lineLength-1)
				{
					workingLine.Append(IMMessageQueue.at(i).Mid(bracketPos+1,lineLength-bracketPos));
				}
				// Remove duplicate static lines updating timestamp
				// If line is not static and last timer has expired we delete it
				// Static lines start with '*' after LOTRO timestamp
				int firstMSGCharPos = timerIndex.at(i).at(2);
				if (firstMSGCharPos < startOfRealMSG)
				{	
					firstMSGCharPos = startOfRealMSG;
				}
				if (IMMessageQueue.at(i)[firstMSGCharPos+1] == '*')
				{
					for (UINT k = 0; k < compiledMessageQueue.size();k++)
					{
						if (IMMessageQueue.at(k) == IMMessageQueue.at(i))
						{
							//Set old line timestamp to new line timestamp
							timerIndex.at(k)[0] = timerIndex.at(i)[0];
							// Delete the new line and timer index
							IMMessageQueue.erase(IMMessageQueue.begin()+i);
							timerIndex.erase(timerIndex.begin()+i);
							// Update the compiled queue for this time around
							compiledMessageQueue[k] = workingLine;
							// Make current working line does not get added compiledMessageQueue
							duplicateStaticLine = true;
							updateQueue = true;
							break; // don't repeat loop, will fail since i is no longer a valid index for IMMessageQueue
						}
					}
				} else if (lineExpiresAfter <= 0)
				{
					IMMessageQueue.erase(IMMessageQueue.begin()+i);
					timerIndex.erase(timerIndex.begin()+i);
				}
			} else if (timerIndex.at(i).size() > 2){
				workingLine = IMMessageQueue.at(i).Right(IMMessageQueue.at(i).GetLength()-(IMMessageQueue.at(i).Find(_T(": "))+2)); // add everything after 'CharName: '
			} else
			{
				workingLine = IMMessageQueue.at(i); // add whole line
			}

			// If the line starts with an exlamation mark and the combat is over we delete it
			if (enemyList.empty() && workingLine[0] == '!')
			{
				IMMessageQueue.erase(IMMessageQueue.begin()+i);
				timerIndex.erase(timerIndex.begin()+i);
			}

			// Add finished line to compiled queue
			if (!duplicateStaticLine)
			{
				// If the line starts with an asterisk or exclamation mark we hide the first char
				if (workingLine[0] == '*' || workingLine[0] == '!')
					workingLine.Delete(0,1);

				compiledMessageQueue.push_back(workingLine);
				updateQueue = true;
			}

		}

		// Parse new combat messages
		if (!combatMessageQueue.empty())
		{
			for (UINT i = 0; i < combatMessageQueue.size();i++)
			{
				CString enemyName;
				// Lines with the word damage in them indicate a combat has started or is underway
				// Don't parse lines that end in Power. , that's mana drain
				// Don't parse lines that end with .damaged, that's equipment wear/breakage
				if (combatMessageQueue.at(i).Find(_T("damage")) > 0 && combatMessageQueue.at(i).Right(6) != _T("Power.") && combatMessageQueue.at(i).Right(8) != _T("damaged."))
				{
					// Mark when combat started and set state

					if (enemyList.empty())
					{
						combatStartTime = combatTime[i][0];
						dmgOutByType.assign(10,0);
						dmgInByType.assign(10,0);
						dmgTypeOutPercentages.assign(10,0);
						dmgTypeInPercentages.assign(10,0);
						selfTotalDmgOut = 0;
						selfTotalDmgIn = 0;
						selfTotalHealOut = 0;
						selfTotalHealIn = 0;
						petTotalDmgOut = 0;
						petTotalDmgIn = 0;
						skirmPetTotalDmgOut = 0;
						skirmPetTotalDmgIn = 0;
						if (lcd->IsConnected())
						{
							lcd->SetVisible(icon, TRUE);
						}
					}
					// Update last damage done time
					combatLastDmgTime = combatTime[i][0];
			
					// Add any new enemies to the list of enemies
					bool enemyListed = false;
				
					// Parse Line
					int dmg;
					int lineForm = parseCombatLine(&knownPetNames, &skirmishPetNames, combatMessageQueue.at(i), &enemyName, &dmg);

					if (enemyName.GetLength() > 0) // Ignore damage messages where there is no enemy listed
					{
						// Check if enemy is in list of enemies, add if not
						for (UINT k = 0; k < enemyList.size();k++)
						{
							if (enemyName == enemyList.at(k))
							{
								enemyListed = true;
							}
						}
						if (!enemyListed)
						{
							enemyList.push_back(enemyName);
						}
					}

					// Depending on what's happening add dmg to correct counter
					switch ( lineForm ) 
					{
						case 1: // 1 == You take dmg
							selfTotalDmgIn = selfTotalDmgIn + dmg;

							// Dmg to mobs by type
							if (combatMessageQueue.at(i).Find(_T("Ancient Dwarf-make damage")) > 0)
							{
								dmgInByType[0] = dmgInByType[0] + dmg;
							} else if (combatMessageQueue.at(i).Find(_T("Beleriand damage")) > 0)
							{
								dmgInByType[1] = dmgInByType[1] + dmg;
							} else if (combatMessageQueue.at(i).Find(_T("Westernesse damage")) > 0)
							{
								dmgInByType[2] = dmgInByType[2] + dmg;
							} else if (combatMessageQueue.at(i).Find(_T("Common damage")) > 0)
							{
								dmgInByType[3] = dmgInByType[3] + dmg;
							} else if (combatMessageQueue.at(i).Find(_T("Fire damage")) > 0)
							{
								dmgInByType[4] = dmgInByType[4] + dmg;
							} else if (combatMessageQueue.at(i).Find(_T("Light damage")) > 0)
							{	
								dmgInByType[5] = dmgInByType[5] + dmg;
							} else if (combatMessageQueue.at(i).Find(_T("Lightning damage")) > 0)
							{	
								dmgInByType[6] = dmgInByType[6] + dmg;
							} else if (combatMessageQueue.at(i).Find(_T("Frost damage")) > 0)
							{	
								dmgInByType[7] = dmgInByType[7] + dmg;
							} else if (combatMessageQueue.at(i).Find(_T("Shadow damage")) > 0)
							{	
								dmgInByType[8] = dmgInByType[8] + dmg;
							} else if (combatMessageQueue.at(i).Find(_T("Acid damage")) > 0)
							{	
								dmgInByType[9] = dmgInByType[9] + dmg;
							}

							// Calculate percentages
							for(UINT m = 0; m < dmgTypeInPercentages.size();m++)
							{
								if (dmgInByType[m] > 0) // Avoid divide by zero
								{
									dmgTypeInPercentages[m] = (float)(100 * dmgInByType[m]) / selfTotalDmgOut;
								} else {
									dmgTypeInPercentages[m] = 0;
								}
							}
							break;
						case 2: // 2 == Pet takes dmg
							petTotalDmgIn = petTotalDmgIn + dmg;
							break;
						case 3: // 3 == Skirmish Pet takes dmg
							skirmPetTotalDmgIn = skirmPetTotalDmgIn + dmg;
							break;
						case 4: // 4 == You damage enemy
							// Total dmg to mobs
							selfTotalDmgOut = selfTotalDmgOut + dmg;

							// Dmg to mobs by type
							if (combatMessageQueue.at(i).Find(_T("Ancient Dwarf-make damage")) > 0)
							{
								dmgOutByType[0] = dmgOutByType[0] + dmg;
							} else if (combatMessageQueue.at(i).Find(_T("Beleriand damage")) > 0)
							{
								dmgOutByType[1] = dmgOutByType[1] + dmg;
							} else if (combatMessageQueue.at(i).Find(_T("Westernesse damage")) > 0)
							{
								dmgOutByType[2] = dmgOutByType[2] + dmg;
							} else if (combatMessageQueue.at(i).Find(_T("Common damage")) > 0)
							{
								dmgOutByType[3] = dmgOutByType[3] + dmg;
							} else if (combatMessageQueue.at(i).Find(_T("Fire damage")) > 0)
							{
								dmgOutByType[4] = dmgOutByType[4] + dmg;
							} else if (combatMessageQueue.at(i).Find(_T("Light damage")) > 0)
							{	
								dmgOutByType[5] = dmgOutByType[5] + dmg;
							} else if (combatMessageQueue.at(i).Find(_T("Lightning damage")) > 0)
							{	
								dmgOutByType[6] = dmgOutByType[6] + dmg;
							} else if (combatMessageQueue.at(i).Find(_T("Frost damage")) > 0)
							{	
								dmgOutByType[7] = dmgOutByType[7] + dmg;
							} else if (combatMessageQueue.at(i).Find(_T("Shadow damage")) > 0)
							{	
								dmgOutByType[8] = dmgOutByType[8] + dmg;
							} else if (combatMessageQueue.at(i).Find(_T("Acid damage")) > 0)
							{	
								dmgOutByType[9] = dmgOutByType[9] + dmg;
							}

							// Calculate percentages
							for(UINT m = 0; m < dmgTypeOutPercentages.size();m++)
							{
								if (dmgOutByType[m] > 0) // Avoid divide by zero
								{
									dmgTypeOutPercentages[m] = (float)(100 * dmgOutByType[m]) / selfTotalDmgOut;
								} else {
									dmgTypeOutPercentages[m] = 0;
								}
							}
							break;
						case 5: // 5 == Pet damages enemy
							petTotalDmgOut = petTotalDmgOut + dmg;
							if (combatMessageQueue.at(i).Find(_T(" Melee Fast Common ")))
							{
								flankedEnemy.push_back(enemyName);
								flankedTimeStamp.push_back(time(NULL));
							}
							break;
						case 6: // 6 == SkirmishPet damages enemy
							skirmPetTotalDmgOut = skirmPetTotalDmgOut + dmg;
							break;
						default: // -1 or other, error, no match found, do nothing for now
							#ifdef _DEBUG // Only warn of ignored lines in debug mode
								DisplayError(_T("UnknownLineFormProbablyPetUnknown"), combatMessageQueue.at(i));
							#endif
								break;
					}

					// Check for any out of order Defeats matching this combat line
					UINT k = 0;
					while (k < outOfOrderDefeats.size())
					{
						for (UINT n = 0; n < enemyList.size();n++)
						{
							if (outOfOrderDefeats.at(k) == enemyList.at(n))
							{
								// Queue up mobs to be removed from combat list, they need to be deleted after all other processing
								eraseList.push_back(n);
								outOfOrderDefeats.erase(outOfOrderDefeats.begin() + k);
								k--;
								break;
							}
						}
						k++;
					}
				} else if (int defPos = combatMessageQueue.at(i).Find(_T(" defeated ")) > 0)
				{
					// Parse Line
					int dmg;
					if (parseCombatLine(&knownPetNames, &skirmishPetNames, combatMessageQueue.at(i), &enemyName, &dmg) == 7) // If combat line is of defeat format
					{
						// If we get a defeated line we add the enemy to a defeated list which is used to clear defeated enemies
						UINT k = 0;
						for (; k < enemyList.size();k++)
						{
							if (enemyName == enemyList.at(k)) // If defeated enemy matches any existing enemy
							{
								// Queue up mobs to be removed from combat list, they need to be deleted after all other processing
								eraseList.push_back(k);
								k++;
								break;
							}
						}

						// Only check for out of order defeats if the line does not match any current enemy
						if (k == 0 || k == enemyList.size())
						{
							// Check for out of order defeat msg if you defeated someone
							if (combatMessageQueue.at(i).Left(5) == _T("Your "))
							{
								outOfOrderDefeats.push_back(enemyName);
							}

							// Check for out of order pet defeat msg
							for (UINT n = 0; n < knownPetNames.size();n++)
							{
								if (knownPetNames.at(n) == combatMessageQueue.at(i).Left(knownPetNames.at(n).GetLength())) //Pet Defeated someone
								{
									outOfOrderDefeats.push_back(enemyName);
								}
							}

							// Check for out of order skirmish pet defeat msg
							for (UINT n = 0; n < skirmishPetNames.size();n++)
							{
								if (skirmishPetNames.at(n) == combatMessageQueue.at(i).Left(skirmishPetNames.at(n).GetLength())) //Skirmish Pet Defeated someone
								{
									outOfOrderDefeats.push_back(enemyName);
								}
							}
						}
					}
				} else if (int defPos = combatMessageQueue.at(i).Find(_T(" succumb ")) > 0) // If succumb
				{
					succumbDetected = true;
				} else if (int defPos = combatMessageQueue.at(i).Find(_T(" heal ")) > 0) // You Healing
				{
					int heals;
					parseCombatLine(&knownPetNames, &skirmishPetNames, combatMessageQueue.at(i), &enemyName, &heals);
					selfTotalHealOut = selfTotalHealOut + heals;
				}
			}
			// Clear Combat Message Queue
			combatMessageQueue.clear();
			combatTime.clear();
		}
		// Clean up after succumb and grace period, needed because of out of order log entries
		if (combatLastDmgTime + combatEndGracePeriod < time(NULL) && succumbDetected)
		{
			// Clear all enemies from enemy list
			enemyList.clear();
			eraseList.clear();
			outOfOrderDefeats.clear();
			succumbDetected = false;
			totalCombatTime = 1; // Reset total Combat time
			if (lcd->IsConnected())
			{
				lcd->SetVisible(icon, FALSE);
			}
		}
		// Remove dead mobs from combat list after grace period , needed because of out of order log entries		
		if (combatLastDmgTime + combatEndGracePeriod < time(NULL) && !enemyList.empty())
		{
			// Sort the list in ascending orcer
			sort( eraseList.begin(), eraseList.end() );
			// Find duplicates and move to end of vector
			vector<int>::iterator newEnd = unique(eraseList.begin(), eraseList.end());
			// Delete the duplicate values from erase list
			eraseList.erase(newEnd, eraseList.end());


			while (eraseList.size() > 0)
			{
					enemyList.erase(enemyList.begin() + eraseList.back());
					eraseList.pop_back();

				if (eraseList.size() > enemyList.size()) // Deal with rogue defeat messages, if things get messy we fall back to timeout
				{
					eraseList.clear();
				}
			}
			// Clear up combat icon and write combat history
			if (enemyList.empty())
			{
				if (lcd->IsConnected())
				{
					lcd->SetVisible(icon, FALSE);
				}
				totalCombatTime = 1; // Reset total combat time
				// Insert combat history 
				combatHistory[0].push_back((UINT)(time(NULL) - combatStartTime));
				combatHistory[1].push_back(selfTotalDmgOut);
			}
		}

		// Clear/end combat if no damage has been done for a period of the inactivity timer
		if (combatLastDmgTime + combatInactivityTimer < time(NULL))
		{
			enemyList.clear();
			eraseList.clear();
			totalCombatTime = 1; // Reset total combat time
			if (lcd->IsConnected())
			{
				lcd->SetVisible(icon, FALSE); // Clear up combat icon
			}
		}

		
		// Calculate dps and craft lines for LCD
		if (!enemyList.empty())
		{ 
			// Clear prev queue
			compiledCombatQueue.clear();
			// Update length of the combat except if it's over but we still have some dmg messages coming through
			if (combatStartTime < time(NULL) && enemyList.size() != eraseList.size())
			{
				totalCombatTime = time(NULL) - combatStartTime;
			}

			CString CQline0,CQline1,CQline2,CQline3,CQline4,CQline5;

			CQline0.Format(_T("A   pdmg: %i"), petTotalDmgOut);
			CQline1.Format(_T("B   pdps: %i"), petTotalDmgOut / totalCombatTime);
			CQline2.Format(_T("W  tdmg: %i"), selfTotalDmgIn);
			CQline3.Format(_T("C   dmg: %i"), selfTotalDmgOut);
			CQline4.Format(_T("F   dps: %i"), selfTotalDmgOut / totalCombatTime);
			CQline5.Format(_T("L   tdps: %i"), selfTotalDmgIn / totalCombatTime);


			int totalHistoryDamage = std::accumulate( combatHistory[1].begin(), combatHistory[1].end(), 0 );
			int totalHistoryTime = std::accumulate( combatHistory[0].begin(), combatHistory[0].end(), 0 );
			if (totalHistoryDamage > 0 && totalHistoryTime > 0)
			{
				setPluginVariable(&pluginVariables, _T("hdps"),  totalHistoryDamage / totalHistoryTime);
				setPluginVariable(&pluginVariables, _T("hdmg"),  totalHistoryDamage);
				setPluginVariable(&pluginVariables, _T("htime"),  totalHistoryTime);
			}

			setPluginVariable(&pluginVariables, _T("ctime"),  (UINT)totalCombatTime);
			setPluginVariable(&pluginVariables, _T("tdmg"),  selfTotalDmgIn);
			setPluginVariable(&pluginVariables, _T("dmg"),  selfTotalDmgOut);
			setPluginVariable(&pluginVariables, _T("dps"),  selfTotalDmgOut / (UINT)totalCombatTime);
			setPluginVariable(&pluginVariables, _T("tdps"),  selfTotalDmgIn / (UINT)totalCombatTime);

			if (petTotalDmgOut + petTotalDmgIn > 0)
			{
				setPluginVariable(&pluginVariables, _T("pdmg"),  petTotalDmgOut);
				setPluginVariable(&pluginVariables, _T("pdps"),  petTotalDmgOut / (UINT)totalCombatTime);
			}
			
			// Show skirmish pet instead of pet if only it is present
			// Show short lines if both pets are present
			if (skirmPetTotalDmgOut + skirmPetTotalDmgIn > 0)
			{
				if  (petTotalDmgOut + petTotalDmgIn < 1)
				{
					CString skirmPetDPS, skirmPetDmg;
					CQline0.Format(_T("A  spdmg: %i"), skirmPetTotalDmgOut);
					CQline1.Format(_T("B  spdps: %i"), skirmPetTotalDmgOut / (UINT)totalCombatTime);
				} else
				{
					// For some reason having both calcs in format line does not work
					int petDPS =  petTotalDmgOut / (UINT)totalCombatTime;
					int spetDPS = skirmPetTotalDmgOut / (UINT)totalCombatTime;

					CQline0.Format(_T("A p/s: %i| %i"), petTotalDmgOut, skirmPetTotalDmgOut);
					CQline1.Format(_T("B p/s: %i| %i"), petDPS, spetDPS);
				}
				setPluginVariable(&pluginVariables, _T("spdmg"),  skirmPetTotalDmgOut);
				setPluginVariable(&pluginVariables, _T("spdps"),  skirmPetTotalDmgOut / (UINT)totalCombatTime);
			}
			// Show healing info instead of dmg info if requested
			if (showHealing)
			{
				CQline3.Format(_T("C  heal: %i"), selfTotalHealOut);
				CQline4.Format(_T("F   hps: %i"), selfTotalHealOut / (UINT)totalCombatTime);
			}

			setPluginVariable(&pluginVariables, _T("heal"),  selfTotalHealOut);
			setPluginVariable(&pluginVariables, _T("hps"),  selfTotalHealOut / (UINT)totalCombatTime);

			// Add damage types to LUA settings file (commmunications file)
			setPluginVariable(&pluginVariables, _T("adwarfdmg"),  dmgOutByType[0]);
			setPluginVariable(&pluginVariables, _T("beldmg"),  dmgOutByType[1]);
			setPluginVariable(&pluginVariables, _T("westdmg"),  dmgOutByType[2]);
			setPluginVariable(&pluginVariables, _T("commondmg"),  dmgOutByType[3]);
			setPluginVariable(&pluginVariables, _T("firedmg"),  dmgOutByType[4]);
			setPluginVariable(&pluginVariables, _T("lightdmg"),  dmgOutByType[5]);
			setPluginVariable(&pluginVariables, _T("lightningdmg"),  dmgOutByType[6]);
			setPluginVariable(&pluginVariables, _T("frostdmg"),  dmgOutByType[7]);
			setPluginVariable(&pluginVariables, _T("shadowdmg"),  dmgOutByType[8]);
			setPluginVariable(&pluginVariables, _T("aciddmg"),  dmgOutByType[9]);
			setPluginVariable(&pluginVariables, _T("tadwarfdmg"),  dmgInByType[0]);
			setPluginVariable(&pluginVariables, _T("tbeldmg"),  dmgInByType[1]);
			setPluginVariable(&pluginVariables, _T("twestdmg"),  dmgInByType[2]);
			setPluginVariable(&pluginVariables, _T("tcommondmg"),  dmgInByType[3]);
			setPluginVariable(&pluginVariables, _T("tfiredmg"),  dmgInByType[4]);
			setPluginVariable(&pluginVariables, _T("tlightdmg"),  dmgInByType[5]);
			setPluginVariable(&pluginVariables, _T("tlightningdmg"),  dmgInByType[6]);
			setPluginVariable(&pluginVariables, _T("tfrostdmg"),  dmgInByType[7]);
			setPluginVariable(&pluginVariables, _T("tshadowdmg"),  dmgOutByType[8]);
			setPluginVariable(&pluginVariables, _T("taciddmg"),  dmgOutByType[9]);

			for (UINT i=0; i < flankedEnemy.size(); i++)
			{
				time_t timeLeft = flankedTimeStamp[i] - time(NULL) + 7; // 7 seconds is the duration of flanks
				numeratedFlankedEnemy.Format(_T("flankedmob%u"),i);
				numeratedFlankedTime.Format(_T("flankedtimer%u"),i);
				if (timeLeft > 0)
				{
					// Create new plugin variables for all flanked enemies
					setPluginVariable(&pluginVariables, numeratedFlankedEnemy, flankedEnemy[i]);
					setPluginVariable(&pluginVariables, numeratedFlankedTime, int(timeLeft)); 
				} else
				{
					//Remove flanked enemies that are no longer in a flanked state
					flankedEnemy.erase(flankedEnemy.begin());
					flankedTimeStamp.erase(flankedTimeStamp.begin());
				}
			}

			compiledCombatQueue.push_back(CQline0);
			compiledCombatQueue.push_back(CQline1);
			compiledCombatQueue.push_back(CQline2);
			compiledCombatQueue.push_back(CQline3);
			compiledCombatQueue.push_back(CQline4);
			compiledCombatQueue.push_back(CQline5);
			// Make sure everything gets sent to the LCD
			updateQueue = true;
		}
	
		// If there have been changes to the queue, Put queue lines onto LCD
		// and clear up any unused lcd fields, also write to plugin data file
		if (updateQueue)
		{
			UINT i = 0;
			// Message queue, real data
			if (!compiledMessageQueue.empty())
			{
				for (; i < compiledMessageQueue.size(); i++)
				{
					CString numeratedkey = _T("Message");
					CString cleanMessageString = compiledMessageQueue[i];
					//Remove up double quotes, they would ruin formatting off plugin data file
					cleanMessageString.Remove('"');
					//Remove semi-colon, causes issues for lotro commands
					cleanMessageString.Remove(';');
					//Remove colon, causes issues for lotro commands
					cleanMessageString.Remove(':');
					numeratedkey.AppendFormat(_T("%d"), i);
					setPluginVariable(&pluginVariables, numeratedkey,  cleanMessageString);

					if (i < fieldRatio)
					{
						if (lcd->IsConnected())
						{
							lcd->SetText(fieldArray[i], compiledMessageQueue[i]);
							lcd->SetProgressBarPosition(pbarArray[i], timerPercentages[i]);
						}

					}
				}
			} 
			// Message queue, empty fields
			for (; i < fieldRatio; i++)
			{
				if (lcd->IsConnected())
				{
					lcd->SetText(fieldArray[i], _T(""));
					lcd->SetProgressBarPosition(pbarArray[i], 100);
				}
			}
			// Combat queue, real data
			if (!compiledCombatQueue.empty())
			{
				for (; i < fields && i < compiledCombatQueue.size(); i++)
				{
					if (lcd->IsConnected())
					{
						lcd->SetText(fieldArray[i], compiledCombatQueue[i]);
						lcd->SetProgressBarPosition(pbarArray[i], dmgTypeOutPercentages[i]);
					}
				}
			} 
			// Combat queue, empty fields
			for (; i < fields; i++)
			{
				if (lcd->IsConnected())
				{
					lcd->SetText(fieldArray[i], _T(""));
					lcd->SetProgressBarPosition(pbarArray[i], 0);
				}
			}

			updateQueue = false;

			#ifndef _DEBUG // Don't automatically find new files in debug mode, debug mode users should use the first button to the left under the G keyboard LCD to search for new files
				// Check if there is a newer plugin data file
				if (lastPluginFileCheck < time(NULL))
				{
					// Needs more work from blocking, etc
					OpenMostRecentFile(&hPluginDataFile, pluginDataDir, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ |FILE_SHARE_WRITE, CREATE_ALWAYS, pluginDataPattern, RECURSIVE);
					lastPluginFileCheck = time(NULL);
				}
			#endif
		}
		
		// Check once per second if there are newer log files than the current ones
		// Don't check automatically while in debug
		#ifndef _DEBUG // Don't automatically find new files in debug mode, debug mode users should use the G button to search for new files
			if (lastFileCheck < time(NULL))
			{
				// Needs more work from blocking, etc
				OpenMostRecentFile(&hIMLogFile, logFileDir, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING, IMLogPattern, NON_RECURSIVE);
				OpenMostRecentFile(&hCombatLogFile, logFileDir, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING, combatLogPattern, NON_RECURSIVE);
				lastFileCheck = time(NULL);
			}
		#endif

		// Render to LCD screen 
		if (lcd->IsConnected())
		{
			lcd->Update();
		}

		// If PluginDataFile exists and we have a valid handle to it we write to it
		if (hPluginDataFile != INVALID_HANDLE_VALUE)
		{
			WriteLotroPluginData(&hPluginDataFile, &pluginVariables);
		}

		// And then sleep
		Sleep(50);
	}
	// Cleanup
    delete lcd;
	CloseFile(&hIMLogFile);
	CloseFile(&hCombatLogFile);
	CloseFile(&hPluginDataFile);
	return 0;
}


int parseCombatLine(vector<CString>* knownPetNames, vector<CString>* skirmishPetNames, CString combatLine, CString* enemyName, int* dmg)
// Parses a string and figures out who the enemy and who is hitting on who
// using a list of known pet names and syntax
{
	/* Formats of all known dmg messages
	1. You hit someone else:
		You wound (the )Enemy Name with ..... for ..... points .....
		You wound (the )Enemy Name for ..... points .....
		You partially hit (the )EnemyName for ..... points .....
		You reflect ..... points ...... to (the )EnemyName.

	2. Someone hits you:
		(The )EnemyName wounds you with ......
		You partially avoided (The )EnemyName's attack and you are hit from ..... for ..... points (notice the spelling error with the capital T)
		(The )EnemyName reflects ...... points of ..... damage to Morale back to you.
		You are wounded for ..... points of ..... damage.

	3. Pet hits someone:
		PetName wounds (the )EnemyName for ..... points .....
		PetName wounds (the )EnemyName with ..... for ..... points .....
		PetName partially hits (the )EnemyName with ..... for ..... points .....
		PetName reflects ..... points of ..... to (the )EnemyName.
		#Flanking Messages
		PetName hits (the )EnemyName with Melee Fast Common (Min, Med, Max) for ..... points of Common damage to Morale.

	4. Someone hits pet:
		(The )EnemyName wounds PetName with ..... for ..... points .....
		(The )EnemyName wounds PetName for ..... points .....
		(The )EnemyName reflects ...... points of ..... damage to Morale back to PetName.
		PetName was wounded for .... points of ..... damage

	5. Skirmish Pet hits someone:
		The SkirmishPet wounds (the )EnemyName with ..... for ..... points .....
		The SkirmishPet partially hits (the )EnemyName with ..... for ..... points .....
		The SkirmishPet reflects ..... points ..... to (the )EnemyName.

	6. Someone hits Skirmish Pet:
		(The )EnemyName wounds the SkirmishPet with ..... for ..... points .....
		(The )EnemyName's attack was partially avoided by the Warrior from .... for ..... points ......
		(The )EnemyName reflects ...... points of ..... damage to Morale back to the SkirmishPet.

	7. Someone is defeated:
		PetName defeated (the )EnemyName.
		Your mighty blow defeated (the )EnemyName.
		(The )EnemyName defeated (the )SkirmishPet.
		The SkirmishPet defeated (the )EnemyName.

	8. You heal someone:
		You heal ..... points of your wounds.
		You heal ..... points of wounds of Pet.
		You heal ..... points of wounds of Someone.
		You heal yourself for ..... Power points.
		Missing Skirmish Pet

	9. Someone heals you: TBD
		SomeoneElse heals you for ..... points of Morale.
		SomeoneElse heals you for ..... Power points.
		The Sigil of Healing heals you for..... points of Morale.
		You are healed for ..... of wound damagepoints.
		Missing Skirmish Pet healing you

	10. Someone heals Pet TBD
		SomeoneElse heals Pet for ..... of morale points.
		SomeoneElse healed Pet for ..... Power points.

	11. Someone heals Skirmish Pet: TBD
		Missing Skirmish Pet
	*/

	// bridge words, find mark start and end
	CString bridgeWords[] = {_T(" for "),_T(" with "),_T(" from "),_T(" points "),_T(" the "),_T("'s attack and you are hit from"),_T(" back to "),_T("."),_T(" to ")};

	vector <int> bridgeWordStartPos;
	vector <int> bridgeWordEndPos;
	for (int i = 0; i < 9; i++)	
	{
		int pos = combatLine.Find(bridgeWords[i]);
		if (pos > 0)
		{
			bridgeWordStartPos.push_back(pos);
			bridgeWordEndPos.push_back(pos + bridgeWords[i].GetLength());
		} else
		{
			bridgeWordStartPos.push_back(-1);
			bridgeWordEndPos.push_back(-1);
		}
	}

	// agression words, find, mark start and end
	CString agressionWords[] = {_T(" heal "),_T(" heals "),_T(" healed "),_T(" wound "),_T(" wounds "),_T(" was wounded "),_T(" wounded "),_T(" partially hit "),_T(" partially hits "),_T("'s attack was partially avoided by "),_T(" partially avoided "),_T(" defeated "),_T(" hit "),_T(" hits "),_T(" reflect "),_T(" reflects ")};

	int agressionWordStartPos = -1;
	int agressionWordEndPos = -1;
	int agressionMatched = -1;
	for (int i = 0; i < 16; i++)
	{
		int pos = combatLine.Find(agressionWords[i]);
		if (pos > 0)
		{
			agressionWordStartPos = pos;
			agressionWordEndPos = pos + agressionWords[i].GetLength();
			agressionMatched = i;
			break;
		}
	}

	if (agressionMatched < 0)
	{
		DisplayError(_T("AgressionNotMatched\n"), combatLine);
		return (-1);
	}

	// Process the line
	int startPos = 0;
	if (combatLine.Left(4) == _T("The "))
		startPos = 4;
	CString Name1 = combatLine.Mid(startPos,agressionWordStartPos-startPos);
	CString Name2;
	int startName = -1,endName = -1, startDmg = -1;
	if ( agressionMatched < 14 && agressionMatched > 2 ) // For everything except reflected attacks and heals
	{
		// Create new array with possibilities, convert to unsigned to ignore -1 as high values during min_element comparison
		unsigned int possibleBridgeWords[] = {bridgeWordStartPos[0],bridgeWordStartPos[1],bridgeWordStartPos[2],bridgeWordStartPos[5],bridgeWordStartPos[7]};
		if ( bridgeWordStartPos[1] > startPos && bridgeWordStartPos[1] < bridgeWordEndPos[4]) // " the " has to come before " with ", otherwise it could be a part of spell name
		{
			bridgeWordEndPos[4] = -1;
			bridgeWordStartPos[4] = -1;
		}
		startName = max(agressionWordEndPos,bridgeWordEndPos[4]);
		endName = *min_element(possibleBridgeWords,possibleBridgeWords+5);
		startDmg = bridgeWordEndPos[0];
	} else if ( agressionMatched > 12 )// reflected attacks
	{
		startName = max(max(bridgeWordEndPos[6],bridgeWordEndPos[4]),bridgeWordEndPos[8]);
		endName = bridgeWordStartPos[7];
		startDmg = agressionWordEndPos;
	} else if ( agressionMatched < 3 )// heals
	{
		if ( agressionMatched == 0 )
		{
			startName = combatLine.Find(_T(" points of wounds of ")) + 21;
			endName = combatLine.Find(_T("."));
			if ( startName == 20 )
			{
				startName = combatLine.Find(_T(" points of ")) + 11;
				endName = combatLine.Find(_T(" wounds.")) - 1 ;
			}
			startDmg = agressionWordEndPos;
		}
	}

	//Handle Initial heal for Sigil of Healing in format "You are healed for ..... of wound damagepoints."
	if ( agressionMatched == 2 && combatLine.Right(19) == _T("wound damagepoints.")) 
	{
			Name2 = "Sigil of Healing";
	} else // Regular Handling
	{
		if (startName < 0)
		{
			DisplayError(_T("SecondNameNotFound\n"), combatLine);
			return (-1);
		}
		Name2 = combatLine.Mid(startName,endName-startName);
	}

	CString dmgString = combatLine.Mid(startDmg,bridgeWordStartPos[3] - startDmg);
	dmgString.Remove(','); // Remove commas from dmg line
	*dmg =  _ttoi(dmgString); // everything from " reflects "/" for " to " points"

	// You
	if (combatLine.Left(4) == _T("You ") || combatLine.Left(5) == _T("Your "))
	{
		*enemyName = Name2;
		if (agressionMatched == 10) // You partially avoided
		{
			if (Name2.Left(4) == _T("The ")) // Fix for spelling bug by Turbine, wrong capitalisation
				*enemyName = Name2.Right(Name2.GetLength()-4);
			return 1;
		} else if (agressionMatched == 11) // You defeated someone
		{
			return 7;
		} else if (agressionMatched == 5) // You take dmg from area/totem
		{
			return 1;
		} else if (agressionMatched == 0) // You heal someone
		{
			return 8;
		}
		return 4; // You wound / You reflect
	}

	if (Name2 == _T("you"))
	{
		*enemyName = Name1; 
		return 1; // The EnemyName wounds you 
	}

	if (agressionMatched == 11) // Somone got defeated, this needs to be done before adding pets as we can get stray defeat lines
	{
		*enemyName = Name2;
		return 7;
	}

	bool reParsePet = false;
	do
	{

		// SkirmishPet
		for (UINT i = 0; i < skirmishPetNames->size();i++)
		{
			if (skirmishPetNames->at(i) == Name1) // SkirmishPet
			{
				*enemyName = Name2;
				if (agressionMatched == 11) // SkirmishPet defeated someone
					return 7;

				return 6; // SkirmishPet wounds
			}

			if (skirmishPetNames->at(i) == Name2) // Enemy wounds SkirmishPet
			{
				*enemyName = Name1;
				return 3;
			}
		}

		// Pet
		for (UINT i = 0; i < knownPetNames->size();i++)
		{
			if (knownPetNames->at(i) == Name1) // Pet
			{
				reParsePet = false;
				*enemyName = Name2;
				if (agressionMatched == 11) // Pet defeated someone
					return 7;

				if (agressionMatched == 5) // Pet was wounded (attacker not defined)
					return 2;

				return 5; // Pet wounds
			}

			if (knownPetNames->at(i) == Name2) // Enemy wounds Pet
			{
				reParsePet = false;
				*enemyName = Name1;
				return 2;
			}
		}
		
		// Try to figure out a missing pet name and add it to list of known pets

		if (startPos > 0 || reParsePet == false) // Line starts with "The " so we can assume it's an enemy
		{
			knownPetNames->push_back(Name1);
			*enemyName = Name2;
			reParsePet = true;
		}

	} while (reParsePet == true);

	if (bridgeWordStartPos[4] > 0) // There is a " the" before second name so we can assume it's an enemy
	{
		knownPetNames->push_back(Name1);
		*enemyName = Name2;
		return 6;
	}

	/* Return Values
	1 == You take dmg
	2 == Pet takes dmg
	3 == Skirmish Pet takes dmg
	4 == You damage enemy
	5 == Pet damages enemy
	6 == SkirmishPet damages enemy
	7 == Enemy defeated
	-1 == Error, no match found
	*/
	return -1;
}

int IndexLines(vector<CString>* inputLines, vector<vector<int>>* timerIndex)
// Index lines for brackets (timestamp and timers)
{
	time_t timeStamp = time(NULL);
	for (UINT i = timerIndex->size(); i < inputLines->size();i++) // For all new lines
	{
		vector<int> index;
		// Start all vectors with timestamp
		index.push_back(int(timeStamp));
		for (int j = 0; j < inputLines->at(i).GetLength();j++) // For every char
		{
			TCHAR curChar = inputLines->at(i).GetAt(j);
			if ( curChar == '[' || curChar == ']')
				index.push_back(j);
		}
		timerIndex->push_back(index);
	}
	
	return 0;
}

int ParseLines(CString inputString, vector<CString>* messageQueue, bool filterDups)
// Parse Cstring, split into lines using CR/LF and return in array of CStrings
{
	int inputLength = inputString.GetLength();
    int lineStart, i = 0;
	ULONG* lineBuffer;
	
	if((lineBuffer = new ULONG[inputLength]) == 0)
	{	
		delete [] lineBuffer; // free memory
		lineBuffer = NULL;
		return 1;
	}
	
	// Ignore leading CR/LF, mark start of real text
	while (inputString[i] == '\r' || inputString[i] =='\n')
		i++;

	lineStart = i;
	// Go through every byte and parse by CR/LF
	for(; i < inputLength; )
	{
		if(inputString[i] == '\r' || inputString[i++] == '\n')
		{
			// Add new line to queue without CR/LF
			CString planeLine = inputString.Mid(lineStart,i-lineStart);
			if ( messageQueue->empty() || planeLine != messageQueue->back() || time(NULL) > (lastLogMessage + 1) || filterDups == false) // deal with duplicates
			{
				if ( planeLine[0] != '#') // Ignore lines that start with '#'
				{
					messageQueue->push_back(planeLine);
					lastLogMessage = time(NULL);
				}
			}
			i++;

			// Ignore extra CR/LF
			while (inputString[i] == '\r' || inputString[i] =='\n')
				i++;
			
			// record where the line starts
			lineStart = i;
		}
	}
	// Add last line if it does not end with CR/LF
	if (lineStart != inputLength)
	{
		CString planeLine = inputString.Mid(lineStart,i-lineStart);
		if ( messageQueue->empty() || planeLine != messageQueue->back() || time(NULL) > (lastLogMessage + 1) || filterDups == false)  // deal with duplicates
			if ( planeLine[0] != '#') // Ignore lines that start with '#'
			{
				messageQueue->push_back(planeLine);
				lastLogMessage = time(NULL);
			}
	}

	delete [] lineBuffer; // free memory
	lineBuffer = NULL;
	return 0;
}

int PopulateLCD(CEzLcd* lcd, HANDLE* fieldArray, HANDLE* pbarArray, HANDLE* icon, int fields, int vSpacing, int hSpacing, int linesPerRow, HINSTANCE hInstance)
// Systematically generates LCD layout with all objects invisible
{
	if (lcd->IsConnected())
	{
		int vPos, hPos;
		// Automatically place objects on lcd based on input, wrap rows, etc
		for(int i = 0; i < fields; i++)
		{
			vPos = i * vSpacing -((i / linesPerRow) * (vSpacing * linesPerRow));
			hPos = i / linesPerRow * hSpacing;
			fieldArray[i] = lcd->AddText(LG_STATIC_TEXT, LG_SMALL, DT_LEFT, hSpacing-4); // 4 pixel spacing
			pbarArray[i] = lcd->AddProgressBar(LG_FILLED);
			lcd->SetOrigin(fieldArray[i],hPos, vPos);
			lcd->SetOrigin(pbarArray[i],hPos, vPos+10);
			lcd->SetProgressBarSize(pbarArray[i], hSpacing-10, 3);
			lcd->SetProgressBarPosition(pbarArray[i], 100);

			// Sword icon to show combat state
			HICON hIcon = static_cast<HICON>(LoadImage(hInstance, _T("Icons\\sword.ico"), IMAGE_ICON, 8, 8, LR_LOADFROMFILE));
			*icon = lcd->AddIcon(hIcon, 8, 8);
			lcd->SetOrigin(*icon, 152, 0);

			// Hide all elements by default
			lcd->SetVisible(pbarArray[i], FALSE);
			lcd->SetVisible(fieldArray[i], FALSE);
			lcd->SetVisible(*icon, FALSE);
		}
	}
	return 0;
}

int ReadNewLines(HANDLE *hFile, CString* logChanges)
// Reads any changes made since last read
{
    ULONG numread;
	char *buffer;
    int length = GetFileSize(*hFile, 0);

	if((buffer = new char[length]) == 0)
	{	
		DisplayError(_T("AllocatingBuffer"));
		CloseHandle(*hFile);
		return 1;
	}

	// Read file into memory
	ReadFile(*hFile, buffer, length, &numread, 0);

	// If there is anything new save only the
	// new characters in the buffer
	// Convert from UTF-8 to UTF-16 for windows native form
	if (numread > 0)
	{
		*logChanges = UTF8toUTF16(CStringA(buffer).Left(numread));
	} else {
		*logChanges = "\0";
	}
	
	delete [] buffer; // free memory
	return 0; // Success
}

int CloseFile(HANDLE *hFile)
{
	CloseHandle(*hFile);
	*hFile = INVALID_HANDLE_VALUE;
	return 0;
}

int OpenMostRecentFile(HANDLE *hFile, CString directoryPath, DWORD dwDesiredAccess,DWORD dwShareMode, DWORD dwCreationDisposition, CString searchPattern, bool recursive)  // default searchPattern is "*" and no recursion
// returns a handle to the file most recently written to that matches the pattern and is under the directory
// If hFile points to an existing file we also check against it.
{
	WIN32_FIND_DATA ffd;
	TCHAR szDir[MAX_PATH];
	HANDLE hFind = INVALID_HANDLE_VALUE;
	size_t length_of_path;
	DWORD dwError=0;
	CString lastFileName;
	CString fullPath = directoryPath + "\\" + searchPattern;
	CString fullPathToFile;
	CString fullPathToDir;
	// Struct to hold most recently "written to" file
	struct FileInfo {
		HANDLE hFind;
		WIN32_FIND_DATA ffd;
		} newest;

	// Check to make sure supplied path is not longer than maximum allowed path
	StringCchLength(fullPath, MAX_PATH, &length_of_path);
	if (length_of_path > (MAX_PATH - 3))
	{
		DisplayError(_T("Directory path is too long.\n"));
		return (-1);
	}

	// Prepare string for use with FindFile functions,
	StringCchCopy(szDir, MAX_PATH, fullPath);

	hFind = FindFirstFile(szDir, &ffd);

	if (hFind != INVALID_HANDLE_VALUE)
	{
		//First file found is always the most recent so far
		newest.ffd = ffd;

		//Check each consecutive file and if it's more recent than newer.ffd we use it as most recent found
		while (FindNextFile(hFind, &ffd) != 0)
		{
			if (CompareFileTime(&ffd.ftLastWriteTime, &newest.ffd.ftLastWriteTime) > 0)
			{
				newest.ffd = ffd;
			}
		}

		//Construct full path to file
		fullPathToFile = directoryPath + "\\" + newest.ffd.cFileName;

		//Open the filehandle passed to the function, check if valid and compare against current best
		if (*hFile != INVALID_HANDLE_VALUE) // If there is a valid file handle
		{
			//Determine file name of currently open file, if there is a newer close current file and open the newer file
			TCHAR pszFilename[MAX_PATH+1];
			HANDLE hFileMap;
			hFileMap = CreateFileMapping(*hFile, NULL, PAGE_READONLY, 0, 1, NULL);

			void* pMem = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 1);
			if(GetMappedFileName (GetCurrentProcess(), pMem, pszFilename, MAX_PATH))
			{
				CString pszFilenameString = pszFilename;
				int pos = pszFilenameString.GetLength() - pszFilenameString.ReverseFind('\\') - 1;
				if ( pszFilenameString.Right(pos) != newest.ffd.cFileName )
				{
					CloseHandle(*hFile);
					*hFile = CreateFile(fullPathToFile, dwDesiredAccess, dwShareMode, 0, dwCreationDisposition, 0, 0);
				}
			}
			 UnmapViewOfFile(pMem);
			 CloseHandle(hFileMap);
		} else
		{
			*hFile = CreateFile(fullPathToFile, dwDesiredAccess, dwShareMode, 0, dwCreationDisposition, 0, 0);
		}
	} else if (recursive == false && dwDesiredAccess == GENERIC_READ || recursive == false && dwCreationDisposition == OPEN_EXISTING )
	{		
		// If no file handle is found and it's not a recursive search return error
		dwError = GetLastError();
		return dwError;
	}

	FindClose(hFind);

	// Recurse into directories
	if ( recursive == true )
	{
		// Prepare string for use with FindFile functions,
		StringCchCopy(szDir, MAX_PATH, directoryPath + CString(_T("\\*.*")));

		// Create handle, check if directory is ok
		hFind = FindFirstFile(szDir, &ffd);
		if (INVALID_HANDLE_VALUE == hFind)
		{
			dwError = GetLastError();
			return dwError;
		}

		do 
		{
			if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
			{
				if (CString(ffd.cFileName) != CString(_T(".")) && CString(ffd.cFileName) != CString(_T("..")))
				{
					CString fullPathToDir = directoryPath + "\\" + ffd.cFileName;
					dwError = OpenMostRecentFile(hFile, fullPathToDir, dwDesiredAccess, dwShareMode, dwCreationDisposition, searchPattern, recursive);
				}
			}
		} while (FindNextFile(hFind, &ffd) != 0);
		FindClose(hFind);
	}

	dwError = GetLastError();
	if (dwError != ERROR_NO_MORE_FILES)
	{
		if (*hFile == INVALID_HANDLE_VALUE)
		{
			// If the defaulit disposition is to create a file even if one does not exist we do so in the given directory using the search pattern as a filename
			if (dwCreationDisposition == CREATE_NEW || dwCreationDisposition == OPEN_ALWAYS || dwCreationDisposition == CREATE_ALWAYS)
			{
				fullPathToFile = directoryPath + "\\" + searchPattern;
				*hFile = CreateFile(fullPathToFile, dwDesiredAccess, dwShareMode, 0, dwCreationDisposition, 0, 0);
			} else 
			{
				DisplayError(_T("CreateFile"));
				CloseHandle(*hFile);
				dwError = GetLastError();
				return dwError;
			}
		}
	}
	return 0;
}

int setPluginVariable(map<CString, CString>* pluginVariables, CString key, CString value)
{
	
	(*pluginVariables)[key] = value;
	return 0;
}

int setPluginVariable(map<CString, CString>* pluginVariables, CString key, int value)
{
	CString stringValue;
	stringValue.Format(_T("%i"), value);
	setPluginVariable(pluginVariables, key,  stringValue);
	return 0;
}

int WriteLotroPluginData(HANDLE *hFile, map<CString, CString>* pluginVariables)
// Writes data from a map  to a lotro data file with key = value
// using dot (".") as a separator to indicate hierachies (objects with child records
{
	ULONG numwritten = 0;
	BOOL fSuccess = FALSE;
	CString buffer = "return \r\n{\r\n";
	//CString formater;
	map<CString, CString>::iterator it;

	DWORD dwPtr = SetFilePointer(*hFile, 0, NULL, FILE_BEGIN);
	if (dwPtr == 0xFFFFFFFF) // Pointer move failed
	{
		DisplayError(_T("PointToStart"));
		CloseHandle(*hFile);
		return 1;
	}

	for (it=pluginVariables->begin() ; it != pluginVariables->end(); it++) // For each variable name
	{
		//formater.Format(_T("%d"), (*it).second);
		buffer.Append(CString(_T("	[\"")) + (*it).first + CString(_T("\"] = \"")) + (*it).second + CString(_T("\",\r\n")));
	}
	buffer.Append(CString(_T("	[\"void\"] = false\r\n}")));

	fSuccess = WriteFile(*hFile, CStringA(buffer), lstrlen(buffer) ,&numwritten,NULL); // would be  lstrlen(buffer) * sizeof(TCHAR) for unicode
	if (!fSuccess)
	{
		DisplayError(_T("WriteFile"));
		CloseHandle(*hFile);
		return 1;
	} else
	{
		fSuccess = SetEndOfFile(*hFile); //Set new EOF at pointer
		if (!fSuccess) // If trunkating file fails because LUA is reading the section we padd with zeros
		{
			UINT filesize = GetFileSize(*hFile, 0);
			UINT paddingSize = filesize - numwritten;
			CString padding;
			for(UINT i = 0; i < paddingSize; i++)
			{
				padding.AppendChar('\x20'); //Pad with whitespace
			}
			fSuccess = WriteFile(*hFile, CStringA(padding), lstrlen(padding) ,&numwritten,NULL); // would be  lstrlen(buffer) * sizeof(TCHAR) for unicode
			if (!fSuccess)
			{
				DisplayError(_T("WriteFilePadding"));
				CloseHandle(*hFile);
				return 1;
			}
		}
	}
	return 0;
}

// Standard Error handling
void DisplayError(LPTSTR lpszFunction, CString extraMessage) // default for extraMessage is ""
{ 
    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    DWORD dw = GetLastError(); 

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, 
        NULL );

	LPTSTR extraMessageStr = const_cast< LPTSTR >( static_cast< LPCTSTR >( extraMessage ) ); 

    lpDisplayBuf = 
        (LPVOID)LocalAlloc( LMEM_ZEROINIT, 
                            ( lstrlen((LPCTSTR)lpMsgBuf)
                              + lstrlen((LPCTSTR)lpszFunction)
                              + 41 // account for format string
							  + lstrlen((LPCTSTR)extraMessage))
                            * sizeof(TCHAR) );
    
    if (FAILED( StringCchPrintf((LPTSTR)lpDisplayBuf, 
                     LocalSize(lpDisplayBuf) / sizeof(TCHAR),
                     _T("%s failed with error code %d as follows:\n%s\n%s"), 
                     lpszFunction, 
                     dw, 
                     lpMsgBuf,
					 extraMessageStr)))
    {
        _tprintf_s(_T("FATAL ERROR: Unable to output error code.\n"));
		TRACE(_T("FATAL ERROR: Unable to output error code.\n"));
    }
    
    _tprintf_s(_T("ERROR: %s\n"), (LPCTSTR)lpDisplayBuf);
	MessageBox(NULL, (LPCWSTR)lpDisplayBuf, (LPCWSTR)L"LotroLcd Critical Error!", MB_ICONERROR | MB_OK);

    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);
}

CStringW UTF8toUTF16(const CStringA& utf8)
{
   CStringW utf16;
   int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
   if (len>1)
   { 
      wchar_t *ptr = utf16.GetBuffer(len-1);
      if (ptr) MultiByteToWideChar(CP_UTF8, 0, utf8, -1, ptr, len);
      utf16.ReleaseBuffer();
   }
   return utf16;
}
